﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Reflection;
using System.Reflection.Emit;

namespace SimpleMap.Setters
{
    public class DynamicILPropertyValueSetter<T> : IValueSetter<T>
    {

        LateBoundPropertySet _setter = null;

        public string Name { get; private set; }

        public DynamicILPropertyValueSetter(PropertyInfo property)
        {

            if (property == null) { throw new ArgumentNullException("property"); }

            this.Name = property.Name;
            _setter = CreateLateboundSetter(property);

        }

        /// <summary>
        /// Sets the value in the target object
        /// </summary>
        /// <param name="target">Object to update</param>
        /// <param name="fieldValue">The value to set</param>
        public void SetValue(T target, object fieldValue)
        {
            if (fieldValue == DBNull.Value)
            {

                _setter(target, null);

            }
            else
            {
                _setter(target, fieldValue);
            }

        }


        /// <summary>
        /// Generates a dynamic binding 
        /// </summary>
        /// <param name="property"></param>
        /// <returns></returns>
        private static LateBoundPropertySet CreateLateboundSetter(PropertyInfo property)
        {
            DynamicMethod method = new DynamicMethod("Set" + property.Name, null, new[] { typeof(object), typeof(object) }, true);

            ILGenerator gen = method.GetILGenerator();

            Type sourceType = property.DeclaringType;
            MethodInfo setter = property.GetSetMethod(true);

            gen.Emit(OpCodes.Ldarg_0); // Load input to stack
            gen.Emit(OpCodes.Castclass, sourceType); // Cast to source type
            gen.Emit(OpCodes.Ldarg_1); // Load value to stack
            gen.Emit(OpCodes.Unbox_Any, property.PropertyType); // Unbox the value to its proper value type
            gen.Emit(OpCodes.Callvirt, setter); // Call the setter method
            gen.Emit(OpCodes.Ret);

            LateBoundPropertySet result = (LateBoundPropertySet)method.CreateDelegate(typeof(LateBoundPropertySet));

            return result;


        }

    }
}
